#include <ArduinoJson.h>
#include <ArduinoJson.hpp>

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include "aliyun_mqtt.h"

#include <EEPROM.h>
// #include <ATCommands.h>
#include "ATcommand.h"
#include "ATcommand.h"


// #include "DHT.h"

#define sensor_Pin   0  
#define DHTPIN 2     
#define DHTTYPE DHT11   
// DHT dht(DHTPIN, DHTTYPE);
float h;
float t;
long s;
// 连接WIFI和密码 
char ssid[32] = "TP-LINK_58a";             // Replace with your WiFi name
char password[64] = "12345678a";   // Replace with your WiFi password
#define EEPROM_SIZE 4096//模拟EEPROM 空间
#define RESET_CNT_ADDR        0 //模拟EEPROM 地址
#define WIFI_SSID_32_ADDR     (RESET_CNT_ADDR+1)  //模拟EEPROM 地址
#define WIFI_PSD_64_ADDR      (WIFI_SSID_32_ADDR+32)//模拟EEPROM 地址
#define PRODUCT_KEY_33ADDR    (WIFI_PSD_64_ADDR+64)//模拟EEPROM 地址
#define DEVICE_NAME_33ADDR      (PRODUCT_KEY_33ADDR+33)//模拟EEPROM 地址
#define DEVICE_SECRET_33ADDR    (DEVICE_NAME_33ADDR+33)//模拟EEPROM 地址
#define REGION_ID_20ADDR    (DEVICE_SECRET_33ADDR+33)//模拟EEPROM 地址
#define PRODUCT_SN_32ADDR    (REGION_ID_20ADDR+20)//模拟EEPROM 地址


//设备的三元组信息
#define PRODUCT_KEY       "a10Awkp85B3"
#define DEVICE_NAME       "AXENT_EU20240529_0002"
#define DEVICE_SECRET     "4c59f3c94ebb0b786dac4b0f7de99b53"
#define REGION_ID         "cn-shanghai"
char product_key[33] = PRODUCT_KEY;             //三元组之一，可以通过AT命令动态修改
char device_name[33] = DEVICE_NAME;             //三元组之一，可以通过AT命令动态修改
char device_secret[33] = DEVICE_SECRET;             //三元组之一，可以通过AT命令动态修改
char region_id[20] = REGION_ID;             //物联网平台地区选择，MQTT服务器连接参数，可以通过AT命令动态修改，德国法兰克福地区id是eu-central-1。

//不需要改 
#define MQTT_SERVER       PRODUCT_KEY".iot-as-mqtt." REGION_ID ".aliyuncs.com"
#define MQTT_PORT         1883
#define MQTT_USRNAME      DEVICE_NAME "&" PRODUCT_KEY

#define CLIENT_ID     "FESA234FBDS24|securemode=3,signmethod=hmacsha1,timestamp=789|"//这里使用技小新可以生成，注意密码和密钥一定要对否则连不上MQTT    "FESA234FBDS24|securemode=3,timestamp=789,signmethod=hmacsha1|"
#define MQTT_PASSWD       "74bc69297d87e17ef3b3fcf9eb96bf9d928cebd4"//这里使用技小新可以生成，注意密码和密钥一定要对否则连不上MQTT 

#define TOILET_SN "E6400103X2209000201"
#define ALINK_TOPIC_PROP_POST     "/" PRODUCT_KEY "/" DEVICE_NAME "/user/data"
// #define ALINK_BODY_FORMAT         "{\"id\":\"AXENT_ZERO_EU\",\"version\":\"2.0\",\"TOILET_SN\":\"" TOILET_SN "\",\"params\":\"%s\"}"
#define ALINK_BODY_FORMAT         "{\"id\":\"AXENT_ZERO_EU\",\"version\":\"241021\",\"method\":\"thing.event.property.post\",\"params\":{\"SN\":\"%s\",\"HEX\":\"%s\"}}"
#define ALINK_TOPIC_PROP_SET      "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/service/property/set" //主题： 物模型属性变更，参考：ESP8266(arduino方式)快速连接阿里云物联网平台（AliYun） https://blog.csdn.net/ziqi5543/article/details/88710552


char product_sn[32] = "E6400103X2209000100";             // axent智能马桶产品序列码

unsigned long lastMs = 0;
WiFiClient espClient;
PubSubClient  mqttClient(espClient);


float soil_data = 0;  

unsigned char print_debug_en =0;//打印调试信息使能开关

//读取eeprom的字符串存储到array数组。返回字符串长度。
unsigned char read_eeprom_string(char *array,int const address,unsigned char length)//读取eeprom的字符串存储到array数组。返回字符串长度。
{
    unsigned char i ;
    for (i = 0; i < length-1; i++)//
    {
      array[i] = EEPROM.read(address+i);//存在地址起始的i字节     
      if(array[i]<=0) return i;//结束符或异常字符退出
    } 
    array[i] = 0;//最后一个结束符
    return i;//退出
}
//写入存储eeprom的字符串。注意不要超越eeprom字符串定义地址的边界
void write_eeprom_string(char *array,int const address,unsigned char length)//写入存储eeprom的字符串。
{
      for (unsigned char i = 0; i < length; i++)//
      {
        EEPROM.write(address+i,array[i]);//存在地址起始的i字节     
        if(array[i]<=0) break;//结束符或异常字符退出
      }
      EEPROM.commit();//提交EEPROM
}
void wifi_config_save(void)//将wifi密码保存起来
{
      write_eeprom_string(ssid,WIFI_SSID_32_ADDR,32);
      write_eeprom_string(password,WIFI_PSD_64_ADDR,64);
}
bool autoConfig()
{
  // WiFi.begin();
  WiFi.begin(ssid, password);
  for (int i = 0; i < 10; i++)
  {
    int wstatus = WiFi.status();
    if (wstatus == WL_CONNECTED)
    {
      Serial.println("AutoConfig Success");
      Serial.printf("GET SSID:%s\r\n", WiFi.SSID().c_str());
      Serial.printf("GET PSW:%s\r\n", WiFi.psk().c_str());
      WiFi.printDiag(Serial);
      return true;
      //break;
    }
    else
    {
      Serial.print("Try connnet to WIFI...");//尝试本地的wifi密码
      Serial.println(wstatus);
      delay(500);//delay(1000); // 这个地方一定要加延时，否则极易崩溃重启
      char serial_rx[500];
      int serial_rx_num=0;
      serial_rx_num = Serial.readBytes(serial_rx,500);//接收最多n字节
      if(serial_rx_num)
      {
        serial_rx[serial_rx_num] = 0;
        // 解析at命令
        parseAtCommand(serial_rx); //解析at命令
      }
    }
  }
  Serial.println("Find wifi Faild!" );//无法找到wifi
  return false;
  //WiFi.printDiag(Serial);
}
void smartConfig()
{
  WiFi.mode(WIFI_STA);
  Serial.println("\r\nWait for Smartconfig");
  WiFi.beginSmartConfig();
  while (1)
  {
    if(print_debug_en) Serial.print(".");
    if (WiFi.smartConfigDone())//通过广播收到了wifi密码
    {
      Serial.println("SmartConfig Success");
      strcpy(ssid,WiFi.SSID().c_str());
      strcpy(password,WiFi.psk().c_str());
      Serial.printf("SSID:%s\r\n", ssid);
      Serial.printf("PSW:%s\r\n", password);
      wifi_config_save();
      WiFi.setAutoConnect(true);  // 设置自动连接    
      if (autoConfig())//wifi尝试连接网络成功就退出
      {
        break;//退出while循环
      }
      else//收到的wifi密码尝试连接失败
      {//WIFI网络连接失败，需要重新进入配网。
        WiFi.stopSmartConfig();
        Serial.println("connect wifi FAIL!");       
        Serial.println("\r\nRestart Smartconfig");
        WiFi.mode(WIFI_STA);
        WiFi.beginSmartConfig();//启动智能配网，开始在接收广播        
      }    
    }
    delay(500);//delay(1000); // 这个地方一定要加延时，否则极易崩溃重启
    // Serial.setTimeout(500); // 设置超时为500毫秒
    char serial_rx[500];
    int serial_rx_num=0;
    serial_rx_num = Serial.readBytes(serial_rx,500);//接收最多n字节
    if(serial_rx_num)
    {
      serial_rx[serial_rx_num] = 0;
      // 解析at命令
      parseAtCommand(serial_rx); //解析at命令
    }
    int wstatus = WiFi.status();
    if (wstatus == WL_CONNECTED)
    {
      Serial.println("WIFI CONNECT Success");
      break;//退出while循环
    }
  }
}

//连接wifi
void wifiInit()
{
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(1000);
        Serial.println("WiFi not Connect");
    }
}
void mqtt_callback(char *topic, byte *payload, unsigned int length)//注册 订阅主题后收到消息的回调函数
{
    Serial.print("Message arrived [");
    Serial.print(topic);
    Serial.print("] ");
    payload[length] = '\0';
    Serial.println((char *)payload);
 
    if (strstr(topic, ALINK_TOPIC_PROP_SET))
    {
        StaticJsonBuffer<100> jsonBuffer;
        JsonObject &root = jsonBuffer.parseObject(payload);
        if (!root.success())
        {
            Serial.println("parseObject() failed");
            return;
        }
    }
}
//mqtt连接
void mqttCheckConnect()
{
    if (!mqttClient.connected())//MQTT没有连接成功
    {        
        if(print_debug_en) Serial.println("mqtt mqttClient.connecting ...");
        // mqttClient.setServer(MQTT_SERVER, MQTT_PORT);   //连接普通MQTT服务器 参数
        // mqttClient.connect(CLIENT_ID, MQTT_USRNAME, MQTT_PASSWD);//通过id用户名密码连接MQTT服务器
        if(connect_aliyun_mqtt(mqttClient, product_key, device_name, device_secret,region_id))//通过三元组连接阿里云物联网平台
        {
            Serial.println("aliyun MQTT connect succeed!");
        }
        delay(1000);

    }else{
      // Serial.println("mqtt mqttClient.connected .");
    }   
    // mqttClient.subscribe(ALINK_TOPIC_PROP_SET);//订阅主题
    //mqttClient.setCallback(mqtt_callback);//注册 订阅主题后收到消息的回调函数
}

// 
// 示例处理函数
void handleTestCmd1(const char* param) {
  if (param == NULL) { printf("ERROR!! Parameter is NULL\n"); return;}
    printf("Handling 'TEST' command with parameter: %s\n", param ? param : "(no parameter)");
 
  if(1){
    // 设置深度睡眠时间（单位：毫秒）
    unsigned long sleepTime = 5 * 1000 * 1000; // 30秒
    Serial.printf("ESP.deepSleep(%d).\n",sleepTime/1000000);
    // 进入深度睡眠模式
    ESP.deepSleep(sleepTime);//需要GPIO16唤醒
  }
  // 进入轻度睡眠模式
  // wifi_station_disconnect();
  // wifi_set_opmode(NULL_MODE);//	set	WiFi	mode	to	null	mode
  // wifi_fpm_set_sleep_type(LIGHT_SLEEP_T);
  // wifi_fpm_open();		//	enable	force	sleep
  // wifi_fpm_set_wakeup_cb(fpm_wakup_cb_func1);
  // wifi_fpm_do_sleep(5 * 1000);
  // ESP.lightSleep();
  // 进入调制解调器睡眠模式
  // WiFi.setSleepMode(WIFI_MODEM_SLEEP);
  //关闭wifi
  // WiFi.mode(WIFI_OFF);
  // // WiFi.forcesleepBegin();  
  // 进入低功耗模式
  // enterLowPowerMode();
  // ESP.deepSleep(sleepTime);//需要GPIO16唤醒
  if(0)  {
        // 关闭WiFi，以节省电量
    WiFi.mode(WIFI_OFF);
    WiFi.forceSleepBegin();
    // 定义唤醒原因
    #define WAKE_REASON_TIMER 1
    // 设置定时器唤醒时间（单位：微秒）
    ESP.deepSleep(6 * 1000000, (WakeMode)WAKE_REASON_TIMER); // 6秒后唤醒
  }
  if(0){//调制解调器睡眠 不稳定，一般概率重启。休眠模块电流降到30mA
    Serial.println("Going to sleep now");
    WiFi.forceSleepBegin(); // Wifi Off
    delay(1000*10); // sleep for 10 seconds
    WiFi.forceSleepWake(); // Wifi On
    Serial.println("Wake up");
  }
 if(0){// void light_sleep()进入轻度睡眠状态，直到引脚 GPIO2 （D4） 连接到 GND,容易堆栈溢出重启
   wifi_station_disconnect();
   wifi_set_opmode_current(NULL_MODE);
   wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); // set sleep type, the above posters wifi_set_sleep_type() didnt seem to work for me although it did let me compile and upload with no errors 
   wifi_fpm_open(); // Enables force sleep
  //  gpio_pin_wakeup_enable(GPIO_ID_PIN(2), GPIO_PIN_INTR_LOLEVEL); // GPIO_ID_PIN(2) corresponds to GPIO2 on ESP8266-01 , GPIO_PIN_INTR_LOLEVEL for a logic low, can also do other interrupts, see gpio.h above
   Serial.println("Going to light_sleep");
   wifi_fpm_do_sleep(10*1000*1000); // 0xFFFFFFF Sleep for longest possible time
   Serial.println("Wake up light_sleep");
 }
  // onWake();
  // ESP.restart();//这个函数是通过向ESP8266的SDK发送信号来重启，实现得更加“柔和”。它能保存当前的运行状态并在重启后恢复，适合在程序中需要重启模块时使用
}
void handleWifiCmd(const char* param)//解析AT指令的wifi名称+密码
{
  if (param == NULL) { printf("ERROR!! Parameter is NULL\n"); return;}
  char * quotestr=extractQuotedString(param,ssid);
  if(quotestr !=NULL){
    printf("\nSSID=\"%s\"\n",ssid);
    quotestr=extractQuotedString(quotestr+1,password);//下一个字符串，密码  
    if(quotestr !=NULL){
      printf("password=\"%s\"\n",password);
      wifi_config_save();      
      WiFi.stopSmartConfig();//收到命令停止智能配网
      WiFi.setAutoConnect(true);  // 设置自动连接    
      WiFi.begin(ssid, password);;//需要重新进入配网。
      
    }else printf("ERROR!! shuld be AT+WIFI=\"ssid\",\"password\"\n");
  }else   printf("ERROR!! shuld be AT+WIFI=\"ssid\",\"password\"\n");
}
void handleCmd3YZ(const char* param)//解析AT指令接收的三元组，格式顺序：AT+3YZ="produckey","device_name","device_secret"
{
  if (param == NULL) { printf("ERROR!! Parameter is NULL\n"); return;}
  char * quotestr=extractQuotedString(param,product_key);//读取下一个字符串
  if (param == NULL) { printf("ERROR!! Parameter is NULL\n"); return;}
  if(quotestr !=NULL){
    printf("\nproduct_key=\"%s\"\n",product_key);
    quotestr=extractQuotedString(quotestr+1,device_name);//读取下一个字符串
    if(quotestr !=NULL){
      printf("device_name=\"%s\"\n",device_name);
      quotestr=extractQuotedString(quotestr+1,device_secret);//读取下一个字符串
      if(quotestr !=NULL){
        printf("device_secret=\"%s\"\n",device_secret);
        write_eeprom_string(product_key,PRODUCT_KEY_33ADDR,33);
        write_eeprom_string(device_name,DEVICE_NAME_33ADDR,33);
        write_eeprom_string(device_secret,DEVICE_SECRET_33ADDR,33);
      }
    }else printf("ERROR!! ");
  }else   printf("ERROR!! shuld be AT+3YZ=\"produckey\",\"device_name\",\"device_secret\"\n");
}
void handleCmdRGEION_ID(const char* param)//解析AT指令接收的RGEION_ID，格式顺序：AT+RGEION_ID="cn-shanghai"
{ 
  if (param == NULL) { printf("ERROR!! Parameter is NULL\n"); return;}
  char * quotestr=extractQuotedString(param,region_id);//读取param的下一个字符串存到
  if(quotestr !=NULL){
    printf("\n region_id=\"%s\"\n",region_id);
    write_eeprom_string(region_id,REGION_ID_20ADDR,20);
  }else   printf("ERROR!! shuld be AT+RGEION_ID=\"RGEION_ID\"\n");
}
void handleCmdReset(const char* param)//解析AT指令接收的三元组，格式顺序：AT+RESET
{
  //ESP.reset();//这是一个硬复位方法，它会直接重置微控制器，可能导致一些寄存器回到初始状态，类似于硬件复位。
  ESP.restart();//这个函数是通过向ESP8266的SDK发送信号来重启，实现得更加“柔和”。它能保存当前的运行状态并在重启后恢复，适合在程序中需要重启模块时使用
}
void handleCmdPRODUCT_SN(const char* param)//解析AT指令接收的PRODUCT_SN，格式顺序：AT+PRODUCT_SN="E6400103X2209000100"
{
  if (param == NULL) { printf("ERROR!! Parameter is NULL\n"); return;}
  char * quotestr=extractQuotedString(param,product_sn);//读取param的下一个字符串存到
  if(quotestr !=NULL){
    printf("\n product_sn=\"%s\"\n",product_sn);
    write_eeprom_string(product_sn,PRODUCT_SN_32ADDR,20);
  }else   printf("ERROR!! shuld be AT+PRODUCT_SN=\"E640XXXXXX...\"\n");
}
void handleCmdDebugEn(const char* param)//解析AT指令接收的PRODUCT_SN，格式顺序：AT+PRODUCT_SN="E6400103X2209000100"
{
  print_debug_en = 1;
  printf("print_debug_en = 1\n");
}
void handleCmdDebugDis(const char* param)//解析AT指令接收的PRODUCT_SN，格式顺序：AT+PRODUCT_SN="E6400103X2209000100"
{
  print_debug_en = 0;
  printf("print_debug_en = 0\n");
}
void handleCmdDeepSleep(const char* param)
{
    // 设置深度睡眠时间（单位：毫秒）
    unsigned long sleepTime = 5 * 1000 * 1000; // 30秒
    Serial.printf("ESP.deepSleep(%d).\n",sleepTime/1000000);
    // 进入深度睡眠模式
    ESP.deepSleep(sleepTime);//需要GPIO16唤醒
  }
void setup() 
{
    Serial.begin(115200);      
    Serial.setTimeout(500); // 设置超时为500毫秒

    // 初始化EEPROM库
    EEPROM.begin(EEPROM_SIZE); // EEPROM_SIZE 通常是4K或更大，根据你的需求设定
    // 读取数据
    int readData = EEPROM.read(RESET_CNT_ADDR);
    Serial.println(readData);
    readData++;//+1
    EEPROM.write(RESET_CNT_ADDR, readData);// 写入数据
    // 提交更改
    EEPROM.commit();// 提交更改

    read_eeprom_string(ssid,WIFI_SSID_32_ADDR,32);
    read_eeprom_string(password,WIFI_PSD_64_ADDR,64);
    read_eeprom_string(product_key,PRODUCT_KEY_33ADDR,33);
    read_eeprom_string(device_name,DEVICE_NAME_33ADDR,33);
    read_eeprom_string(device_secret,DEVICE_SECRET_33ADDR,33);
    read_eeprom_string(region_id,REGION_ID_20ADDR,20);
    read_eeprom_string(product_sn,PRODUCT_SN_32ADDR,32);
    Serial.printf("EE SSID:%s\r\n", ssid);
    Serial.printf("EE PSW:%s\r\n", password);
    Serial.printf("EE product_key:%s\r\n", product_key);
    Serial.printf("EE device_name:%s\r\n", device_name);
    if(print_debug_en) Serial.printf("EE device_secret:%s\r\n", device_secret);//重要数据保护
    Serial.printf("EE region_id:%s\r\n", region_id);
    if(region_id[0]==0xFF){strcpy(region_id,"eu-central-1");}//如果没有设置地区，默认设置为eu-central-1德国法兰克福
    Serial.printf("EE product_sn:%s\r\n", product_sn);

    // 注册AT指令 TEST     
    printf("register AT command ：\n");
    if (registerAtCommand("TEST", handleTestCmd1) != 0) {
        printf("Failed to register TEST!\n");
    }
    // 注册AT指令 TEST
    if (registerAtCommand("TEST2", handleTestCmd1) != 0) {
        printf("Failed to register TEST2\n");
    }
    // 注册AT指令 WIFI
    if (registerAtCommand("WIFI", handleWifiCmd) != 0) {
        printf("Failed to register WIFI!\n");
    }
    // 注册AT指令 WIFI
    if (registerAtCommand("3YZ", handleCmd3YZ) != 0) {
        printf("Failed to register 3YZ!\n");
    }
    // 注册AT指令 WIFI
    if (registerAtCommand("RESET", handleCmdReset) != 0) {
        printf("Failed to register 3YZ!\n");
    }
    // 注册AT指令 WIFI
    if (registerAtCommand("RGEION_ID", handleCmdRGEION_ID) != 0) {
        printf("Failed to register RGEION_ID!\n");
    }    
    // 注册AT指令 WIFI
    if (registerAtCommand("PRODUCT_SN", handleCmdPRODUCT_SN) != 0) {
        printf("Failed to register PRODUCT_SN!\n");
    }    
    // 注册AT指令 DEBUGEN
    if (registerAtCommand("DEBUG=1", handleCmdDebugEn) != 0) {
        printf("Failed to register DEBUGEN!\n");
    }    
    // 注册AT指令 DEBUGDIS
    if (registerAtCommand("DEBUG=0", handleCmdDebugDis) != 0) {
        printf("Failed to register DEBUGDIS!\n");
    }    
    // 注册AT指令 DEBUGDIS
    if (registerAtCommand("DeepSleep", handleCmdDeepSleep) != 0) {
        printf("Failed to register DeepSleep!\n");
    }    
    
    // 测试解析
    // parseAtCommand("AT+TEST=Hello"); // 应调用handleTestCmd
    
    // 设置睡眠模式（可选参数：WIFI_NONE_SLEEP、WIFI_LIGHT_SLEEP、WIFI_MODEM_SLEEP ）
    WiFi.setSleepMode(WIFI_LIGHT_SLEEP);  
    // wifiInit();
    if (!autoConfig())//wifi配网
    {
      Serial.println("Start smartConfig.");
      smartConfig();
    }    

    
    mqttCheckConnect();
    
    

}

void read_data()
{
  float read_h = 12;//dht.readHumidity();//湿度
  float read_t = 35;//dht.readTemperature();//温度
  h = read_h;
  t = read_t;
  Serial.print("湿度：");
  Serial.print(h);
  Serial.println("%");
  Serial.print("温度：");
  Serial.println(t);
}

void loop()
{
   s=millis();
   
    
  char serial_rx[500];
  int serial_rx_num=0;
  serial_rx_num = Serial.readBytes(serial_rx,500);//接收最多32字节
  if(serial_rx_num)
  {
      serial_rx[serial_rx_num] = 0;
    if(print_debug_en) {
      Serial.print("received ");    
      Serial.print(serial_rx_num);
      Serial.print("bytes=");
      Serial.print(serial_rx);   
      Serial.println();
    }
    // 解析at命令
    parseAtCommand(serial_rx); //解析at命令

    char param[512];
    char jsonBuf[1024];
    char topic[64]="";
    // if(serial_rx[0] == 0x02)
      for(int i=0;i<serial_rx_num;i++)
        sprintf(param+i*3, "%02X ", serial_rx[i]);
    sprintf(jsonBuf, ALINK_BODY_FORMAT,product_sn,param);
    if(print_debug_en) Serial.println(jsonBuf);
    strcat(topic,"/");
    strcat(topic,product_key);
    strcat(topic,"/");
    strcat(topic,device_name);
    strcat(topic,"/user/data");
    mqttClient.publish(topic, jsonBuf);//向云端上报消息
  }
    // Serial.println( millis() );
    if (millis() - lastMs >= 30000)//每30秒检测一次
    {
        lastMs = millis();
        mqttCheckConnect(); 
        // mqttIntervalPost();
    }
    mqttClient.loop();
}
